package Aprior;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.TreeSet;

public class Aprior {
	
	
	class SetComparator implements Comparator<LargeSet> {
		public int compare(LargeSet o1, LargeSet o2) {
			// TODO Auto-generated method stub
			
			if(o1.support > o2.support)
				return -1;
			else if (o1.support == o2.support)
				return 0;
			else
				return 1;
		}
		}
	
	class RuleComparator implements Comparator<AssociateRule>
	{

		@Override
		public int compare(AssociateRule o1, AssociateRule o2) {
			// TODO Auto-generated method stub
			if(o1.confidence > o2.confidence)
				return -1;
			else if (o1.confidence == o2.confidence)
				return 0;
			else 
				return 1;
		}
	}
	
	
	
	class AssociateRule extends TreeMap<LargeSet, String>
	{
		public AssociateRule(LargeSet lHS, String right,
				double confidence, double support) {
			super();
			this.lHS = lHS;
			this.right = right;
			this.confidence = confidence;
			this.support = support;
		}
		
		LargeSet lHS;
		String right;
		double confidence;
		double support;
	}
	
	
	
	class LargeSet extends TreeSet<String> {
		int support;
		HashSet<Integer>transactions;

		
		private int calculateSupport()
		{
			HashSet<Integer> trans = null;
			int i = 0;
			for(String s: this)
			{
				HashSet<Integer> _trans = (HashSet<Integer>)itemToTransactionMap.get(s).clone();
				
				//System.out.println("See the "+i+" item's transactions:");
				//System.out.println(_trans);
				//System.out.println();
				
				if(i == 0)
					trans = _trans;
				else
					trans.retainAll(_trans);
				i++;
			}
			this.transactions = trans;
			this.support = trans.size();
			return this.support;
			
		}
		
		
		@Override
		public boolean equals(Object o) {
			LargeSet anotherSet = (LargeSet)o;
			boolean flag = true;
			
			if(this.size() != anotherSet.size())
				return false;
			for(String s: this)
			{
				if(!anotherSet.contains(s))
					return false;
			}
			return true;
		}
				
		

		public LargeSet(HashSet<Integer> transactions) {
			super();
			this.transactions = transactions;
			support = transactions.size();
		}
		
		public LargeSet(LargeSet anotherSet)
		{
			this.transactions = new HashSet<Integer>();
			this.transactions.addAll(anotherSet.transactions);
			this.addAll(anotherSet);
			this.support = this.transactions.size();
		}
		

		public LargeSet incorporate(LargeSet anotherSet)
		{
			LargeSet thisCopy = new LargeSet(this);
			LargeSet thatCopy = new LargeSet(anotherSet);
			
			thatCopy.removeAll(thisCopy);
			
			//System.out.println("This copy's nT: "+thisCopy.transactions);
			//System.out.println("That copy's nt: "+thatCopy.transactions);
			
			
			thisCopy.transactions.retainAll(thatCopy.transactions);
			
			//System.out.println("The after retain: "+thisCopy.transactions);
			
			thisCopy.addAll(thatCopy);
			thisCopy.support = thisCopy.transactions.size();
			
			return thisCopy;
		}
	}

	
	
	double sThred;
	double cThred;
	
	int n_sThred;
	
	
	int nTransactions;
	int nItems;
	
	String inputFile;
		
	
	TreeSet<AssociateRule> rules;
	TreeSet<LargeSet> largeSets;
	HashSet<LargeSet> lastLevelSets;
	
	SetMap<String, Integer> itemToTransactionMap;
	
	
	int level = 1;
	
	HashSet<String> items;
	
	private void preprocess()
	{
		BufferedReader bf = null;
	
		try {
			bf = new BufferedReader(new FileReader(inputFile));
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}
		
		
		String line;
		try {
			while((line = bf.readLine()) != null)
			{
				nTransactions ++;

				String[] _items = line.split(",");
				for(String s: _items)
				{
					if(!items.contains(s))
					{
						items.add(s);
						nItems++;
					}
					
					itemToTransactionMap.putElement(s, nTransactions);
					
				}
			}
/*			for(String item: itemToTransactionMap.keySet())
			{
				System.out.println(item);
				System.out.println(itemToTransactionMap.get(item));
				System.out.println();
			}
	*/			
				
				
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		n_sThred = (int)(sThred * nTransactions);
		
		for(String item: items)
		{
			if(itemToTransactionMap.get(item).size() > n_sThred)
			{
				LargeSet set = new LargeSet(itemToTransactionMap.get(item));
				set.add(item);
				lastLevelSets.add(set);
			}
		}
	}
	
	private void process()
	{
		while(!lastLevelSets.isEmpty())
		{
			level ++;
			HashSet<LargeSet> newLevelSets = new HashSet<LargeSet>(); 
			
			for(LargeSet set: lastLevelSets)
			{
				for(LargeSet set1: lastLevelSets)
				{
					if(!set.equals(set1))
					{
						LargeSet newSet = set.incorporate(set1);
						if(newSet.support > n_sThred && newSet.size() == level)
						{
							boolean flag = true;
							for(LargeSet lgset: newLevelSets)
							{
								if(lgset.equals(newSet))
								{
									flag = false;
									break;
								}
							}
							if(flag)							
							{
								//System.out.println(newSet+"  "+newSet.size());
								newLevelSets.add(newSet);
							}
							
						}
					}
				}
			}
			largeSets.addAll(newLevelSets);	
			lastLevelSets = newLevelSets;
		}
	}
	
	private void associationRulesGen(){
		
		//System.out.println("==============>Begin Associate Generation");
		//now we have all the large itemSets and their corresponding supports. 
		//use them to generate association rules.
		for(LargeSet lgSet : largeSets){
						
			for(String item: lgSet){
				LargeSet LHS = new LargeSet(lgSet);
				LHS.remove(item);
				
				int LHSsupport = 0;
				int totalSupport = 0;
			
				LHSsupport = LHS.calculateSupport();
				
				totalSupport = lgSet.support;
				
				float conf = (float)totalSupport/LHSsupport;
				float relativeSupport = (float)totalSupport/nTransactions;
				
				if(conf >= cThred){
				
					rules.add(new AssociateRule(LHS, item, conf, relativeSupport));
					
					/*for(String s: LHS){
						System.out.print(s + " ");
					}
					System.out.println(" => "+item+", conf: "+conf+", support: "+relativeSupport);
				*/
				}
			}
		}
	}
	

	private void output()
	{
		FileWriter fw = null;
		PrintWriter pw = null;
		try {
			fw = new FileWriter("output.txt");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		pw = new PrintWriter(fw);
		
		pw.println("==Large itemsets (min_sup="+sThred+")");
		for(LargeSet lgset: largeSets)
		{
			pw.println(lgset+","+(double)lgset.support/nTransactions);
		}
		pw.println("==High-confidence association rules (min_conf="+cThred+")");
		for(AssociateRule rule: rules)
		{
			pw.println(rule.lHS+" => ["+rule.right+"](Conf: "+rule.confidence+", Supp: "+rule.support+")");
		}
		
		try {
			fw.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		pw.flush();
		try {
			fw.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		pw.close();
	}
	
	

	public Aprior(double sThred, double cThred, String inputFile) {
		super();
		this.sThred = sThred;
		this.cThred = cThred;
		this.n_sThred = 0;
		this.nTransactions = 0;
		this.nItems = 0;
		this.inputFile = inputFile;
		this.largeSets = new TreeSet<LargeSet>(new SetComparator());
		this.rules = new TreeSet<AssociateRule>(new RuleComparator());
		this.lastLevelSets = new HashSet<LargeSet>();
		this.itemToTransactionMap = new SetMap<String, Integer>();
		this.level = 1;
		this.items = new HashSet<String>();
	}

	public static void main(String[] args)
	{
		
		Aprior aprior = new Aprior(Double.parseDouble(args[1]), Double.parseDouble(args[2]),args[0]);
		aprior.preprocess();
		aprior.process();
		aprior.associationRulesGen();
		aprior.output();
	}
	

}
